﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.Mocks;
using Microsoft.Bot.Schema;
using RichardSzalay.MockHttp;

namespace Microsoft.Bot.Builder.AI.Luis.Testing
{
    /// <summary>
    /// Test class for creating cached LUIS responses for testing.
    /// </summary>
    /// <remarks>
    /// This will either use a cached LUIS response or generate a new one by calling LUIS.
    /// </remarks>
    public class MockLuisRecognizer : Recognizer
    {
        private string _responseDir;
        private LuisAdaptiveRecognizer _recognizer;

        /// <summary>
        /// Initializes a new instance of the <see cref="MockLuisRecognizer"/> class.
        /// </summary>
        /// <param name="recognizer">LUIS recognizer definition.</param>
        /// <param name="resourceDir">Where the settings file generated by lubuild is found.</param>
        /// <param name="name">Name of the LUIS model.</param>
        public MockLuisRecognizer(
            LuisAdaptiveRecognizer recognizer,
            string resourceDir,
            string name)
        {
            _recognizer = recognizer;
            _responseDir = Path.Combine(resourceDir, "cachedResponses", name);
            if (!Directory.Exists(_responseDir))
            {
                Directory.CreateDirectory(_responseDir);
            }
        }

        /// <inheritdoc/>
        public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialogContext, Activity activity, CancellationToken cancellationToken = default, Dictionary<string, string> telemetryProperties = null, Dictionary<string, double> telemetryMetrics = null)
        {
            HttpClientHandler newHandler = null, oldHandler = _recognizer.HttpClient;
            
            // Used for ResponsePath
            var recognizer = _recognizer.RecognizerOptions(dialogContext);
            foreach (var middware in dialogContext.Context.Adapter.MiddlewareSet)
            {
                if (middware is TelemetryLoggerMiddleware telemetryMiddleware)
                {
                    _recognizer.TelemetryClient = telemetryMiddleware.TelemetryClient;
                }
            }

            recognizer.IncludeAPIResults = true;

            var middleware = dialogContext.Context.TurnState.Get<MockHttpRequestMiddleware>();
            if (middleware == null)
            {
#pragma warning disable CA2000 // Dispose objects before losing scope
                var mockHandler = new MockHttpMessageHandler();
#pragma warning restore CA2000 // Dispose objects before losing scope
                mockHandler.Fallback.Respond((request) => FallbackAsync(request, activity.Text, recognizer, cancellationToken).GetAwaiter().GetResult());
                newHandler = new MockedHttpClientHandler(mockHandler);
            }
            else
            {
                middleware.SetFallback((request) => FallbackAsync(request, activity.Text, recognizer, cancellationToken).GetAwaiter().GetResult());
                newHandler = new MockedHttpClientHandler(dialogContext.Context.TurnState.Get<HttpMessageHandler>());
            }

            _recognizer.HttpClient = newHandler;
            var result = await _recognizer.RecognizeAsync(dialogContext, activity, cancellationToken, telemetryProperties, telemetryMetrics).ConfigureAwait(false);
            _recognizer.HttpClient = oldHandler;
            if (middleware != null)
            {
                middleware.SetFallback(null);
            }

            return result;
        }

        private string ResponsePath(string utterance, LuisRecognizerOptionsV3 recognizer)
        {
            var hash = utterance.StableHash();
            if (recognizer.ExternalEntityRecognizer != null)
            {
                hash ^= "external".StableHash();
            }

            if (recognizer.IncludeAPIResults)
            {
                hash ^= "api".StableHash();
            }

            if (recognizer.LogPersonalInformation)
            {
                hash ^= "personal".StableHash();
            }

            var options = recognizer.PredictionOptions;
            if (options.DynamicLists != null)
            {
                foreach (var dynamicList in options.DynamicLists)
                {
                    hash ^= dynamicList.Entity.StableHash();
                    foreach (var choices in dynamicList.List)
                    {
                        hash ^= choices.CanonicalForm.StableHash();
                        foreach (var synonym in choices.Synonyms)
                        {
                            hash ^= synonym.StableHash();
                        }
                    }
                }
            }

            if (options.ExternalEntities != null)
            {
                foreach (var external in options.ExternalEntities)
                {
                    hash ^= external.Entity.StableHash();
                    hash ^= external.Start.ToString(CultureInfo.InvariantCulture).StableHash();
                    hash ^= external.Length.ToString(CultureInfo.InvariantCulture).StableHash();
                }
            }

            if (options.IncludeAllIntents)
            {
                hash ^= "all".StableHash();
            }

            if (options.IncludeInstanceData)
            {
                hash ^= "instance".StableHash();
            }

            if (options.Log ?? false)
            {
                hash ^= "log".StableHash();
            }

            if (options.PreferExternalEntities)
            {
                hash ^= "prefer".StableHash();
            }

            if (options.Slot != null)
            {
                hash ^= options.Slot.StableHash();
            }

            if (options.Version != null)
            {
                hash ^= options.Version.StableHash();
            }

            return Path.Combine(_responseDir, $"{hash}.json");
        }

        private async Task<HttpResponseMessage> FallbackAsync(HttpRequestMessage request, string utterance, LuisRecognizerOptionsV3 recognizer, CancellationToken cancellationToken)
        {
            var response = ResponsePath(utterance, recognizer);
            if (File.Exists(response))
            {
                var luisResult = File.ReadAllText(response);
                return new HttpResponseMessage
                {
                    Content = new StringContent(luisResult, Encoding.UTF8, "application/json"),
                };
            }
            else
            {
                HttpResponseMessage result = null;
                using (var client = new HttpClient())
                    using (var clonedRequest = await MockedHttpClientHandler.CloneHttpRequestMessageAsync(request).ConfigureAwait(false))
                    {
                        result = await client.SendAsync(clonedRequest, cancellationToken).ConfigureAwait(false);
                    }

                var luisResult = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
                File.WriteAllText(response, luisResult);
                return result;
            }
        }
    }
}
